import json
import socket
import hotupdate
from urllib.parse import unquote, quote
from email import parser, policy
from email.message import Message
import urllib.parse
import hashlib
import KBEngine
import KBEDebug as log
import time

STATUS_CODES = {
    100: "Continue",
    101: "Switching Protocols",
    102: "Processing",
    200: "OK",
    201: "Created",
    202: "Accepted",
    203: "Non-Authoritative Information",
    204: "No Content",
    205: "Reset Content",
    206: "Partial Content",
    207: "Multi-Status",
    208: "Already Reported",
    226: "IM Used",
    300: "Multiple Choices",
    301: "Moved Permanently",
    302: "Found",
    303: "See Other",
    304: "Not Modified",
    305: "Use Proxy",
    307: "Temporary Redirect",
    308: "Permanent Redirect",
    400: "Bad Request",
    401: "Unauthorized",
    402: "Payment Required",
    403: "Forbidden",
    404: "Not Found",
    405: "Method Not Allowed",
    406: "Not Acceptable",
    407: "Proxy Authentication Required",
    408: "Request Timeout",
    409: "Conflict",
    410: "Gone",
    411: "Length Required",
    412: "Precondition Failed",
    413: "Request Entity Too Large",
    414: "Request-URI Too Long",
    415: "Unsupported Media Type",
    416: "Requested Range Not Satisfiable",
    417: "Expectation Failed",
    418: "I'm a teapot",
    422: "Unprocessable Entity",
    423: "Locked",
    424: "Failed Dependency",
    426: "Upgrade Required",
    428: "Precondition Required",
    429: "Too Many Requests",
    431: "Request Header Fields Too Large",
    451: "Unavailable For Legal Reasons",
    500: "Internal Server Error",
    501: "Not Implemented",
    502: "Bad Gateway",
    503: "Service Unavailable",
    504: "Gateway Timeout",
    505: "HTTP Version Not Supported",
    506: "Variant Also Negotiates",
    507: "Insufficient Storage",
    508: "Loop Detected",
    510: "Not Extended",
    511: "Network Authentication Required",
}


class Request(hotupdate.Reload):
    def __init__(self, r):
        hotupdate.Reload.__init__(self)
        self.content = r
        log.DEBUG_MSG("Request: content=%s" % (self.content))
        # self.method = r.split()[0]
        # self.path = r.split()[1]
        # self.body = r.split('\r\n\r\n', 1)[1]
        self.jsondata = self._parseData()
        # log.DEBUG_MSG("Request: method=%s" % (self.method))
        # log.DEBUG_MSG("Request: path=%s" % (self.path))
        # log.DEBUG_MSG("Request: body=%s" % (self.body))
        log.DEBUG_MSG("Request: jsondata=%s" % (self.jsondata))

    def from_body(self):
        return self._parse_parameter(self.body)

    def parse_path(self):
        index = self.path.find('?')
        if index == -1:
            return self.path, {}
        else:
            path, query_string = self.path.split('?', 1)
            query = self._parse_parameter(query_string)
            return path, query

    @property
    def headers(self):
        header_content = self.content.split('\r\n\r\n', 1)[0].split('\r\n')[1:]
        result = {}
        for line in header_content:
            k, v = line.split(': ')
            result[quote(k)] = quote(v)
        return result

    @staticmethod
    def _parse_parameter(parameters):
        args = parameters.split('&')
        query = {}
        for arg in args:
            k, v = arg.split('=')
            query[k] = unquote(v)

        return query

    def _parseData(self):
        if len(self.content) != 0:
            index0 = self.content.find("{")
            index1 = self.content.rfind("}")
            if index0 != -1 and index1 != -1:
                return self.content[index0:(index1 + 1)]
        return ""

    def getResponse(self, code, message=''):
        data = {
            'code': code,
            'message': message
        }
        content = json.dumps(data)
        response = 'HTTP/1.1 %d %s\r\n' % (code, STATUS_CODES.get(code, "UNKNOWN RESPONSE"))
        response += 'Content-Type: application/json\r\n'
        response += 'Content-Length: %d\r\n' % len(content)
        response += '\r\n'
        response += content
        return response

    def getResponseTxt(self, code, msg):
        response = 'HTTP/1.1 %d %s\r\n' % (code, STATUS_CODES.get(code, "UNKNOWN RESPONSE"))
        response += 'Content-Type: application/text\r\n'
        response += 'Content-Length: %d\r\n' % len(msg)
        response += '\r\n'
        response += msg
        return response


class BasePoller(hotupdate.Reload):
    """
    演示：
    可以向kbengine注册一个socket，由引擎层的网络模块处理异步通知收发。
    用法:
    from Poller import Poller
    poller = Poller()

    开启(可在onBaseappReady执行)
    poller.start("localhost", 12345)

    停止
    poller.stop()
    """

    def __init__(self):
        hotupdate.Reload.__init__(self)
        self._socket = None
        self._clients = {}
        self.whiteList = ["127.0.0.1", '192.168.3.4']

    def start(self, addr, port):
        """
        virtual method.
        """
        self._socket = socket.socket()
        self._socket.bind((addr, port))
        self._socket.listen(10)

        log.INFO_MSG("LobbyPoller::start: %s" % str(self._socket))
        KBEngine.registerReadFileDescriptor(self._socket.fileno(), self.onRecv)

    def stop(self):
        if self._socket:
            KBEngine.deregisterReadFileDescriptor(self._socket.fileno())
            self._socket.close()
            self._socket = None

    def onWrite(self, fileno):
        log.DEBUG_MSG("Poller::onWrite ***************************")

    def onRecv(self, fileno):
        if self._socket.fileno() == fileno:
            sock, addr = self._socket.accept()
            self._clients[sock.fileno()] = (sock, addr)
            KBEngine.registerReadFileDescriptor(sock.fileno(), self.onRecv)
            log.DEBUG_MSG("Poller::onRecv: new channel[%s/%i]" % (addr, sock.fileno()))
        else:
            sock, addr = self._clients.get(fileno, None)
            if sock is None:
                return

            # 白名单
            if addr[0] not in self.whiteList:
                log.WARNING_MSG("Poller::onRecv: %s/%i not in whitelist!" % (addr, sock.fileno()))
                KBEngine.deregisterReadFileDescriptor(sock.fileno())
                sock.close()
                del self._clients[fileno]
                return

            data = sock.recv(2048)

            if len(data) == 0:
                log.DEBUG_MSG("Poller::onRecv: %s/%i disconnect!" % (addr, sock.fileno()))
                KBEngine.deregisterReadFileDescriptor(sock.fileno())
                sock.close()
                del self._clients[fileno]
                return

            log.DEBUG_MSG("Poller::onRecv: %s/%i get data, size=%i" % (addr, sock.fileno(), len(data)))
            self.processData(sock, fileno, data)

    def processData(self, sock, fileno, datas):
        """
        处理接收数据
        """
        pass


class MailPoller(BasePoller):

    def processData(self, sock, fileno, datas):
        """
        处理接收数据
        """
        log.INFO_MSG("prcessData: %s" % datas)
        data_src = datas.decode('utf-8')
        log.INFO_MSG("prcessData: decode=%s" % (data_src))

        try:
            req = Request(data_src)
            if len(req.jsondata) == 0:
                self.closeRequest(req, sock, fileno, 400, f'没有json数据？')
        except Exception as e:
            log.ERROR_MSG(f'MailPoller parse request error: {e}')
            self.closeRequest(req, sock, fileno, f'解析请求错误：{e}')
        else:
            try:
                lobby = KBEngine.baseAppData['Lobby']
                json_data = json.loads(req.jsondata)
                command = json_data['command']
                if command == 400:  # 查询邮件
                    email_id = int(json_data['emailId'])
                    if email_id in lobby.emails:
                        self.closeRequest(req, sock, fileno, 200, json.dumps(lobby.emails[email_id]))
                    else:
                        self.closeRequest(req, sock, fileno, 400, f'没有找到邮件')
                elif command == 500:  # 撤回邮件
                    email_id = int(json_data['emailId'])
                    if email_id in lobby.emails:
                        del lobby.emails[email_id]
                        self.closeRequest(req, sock, fileno, 200, '撤回成功')
                    else:
                        self.closeRequest(req, sock, fileno, 400, f'没有找到邮件')
                else:
                    # 100-发送个人邮件、200-发送全服邮件、300-发送公告邮件
                    json_data['persistentTime'] = int(json_data['persistentTime'])
                    email_id = KBEngine.genUUID64()
                    json_data['emailId'] = email_id
                    lobby.onProcessData(json_data)
            except Exception as e1:
                log.ERROR_MSG(f'MailPoller lobby processData error: {e1}')
                self.closeRequest(req, sock, fileno, 400, f'解析数据错误：{e1}')
            else:
                if command == 100 or command == 200 or command == 300:
                    self.closeRequest(req, sock, fileno, 200, {'emailId': email_id})
                else:
                    self.closeRequest(req, sock, fileno, 200, f'操作成功')

    def closeRequest(self, req, sock, fileno, code, msg):
        if fileno not in self._clients:
            print(f'already close {msg}')
            return
        response = req.getResponse(code, msg)
        sock.send(response.encode('utf-8'))
        KBEngine.deregisterReadFileDescriptor(sock.fileno())
        sock.close()
        del self._clients[fileno]


ENTER = "\n"
CLRF = "\r\n"


def http_parse(http_pkg):
    """
    http报文初步解析函数
    参数：http报文字符串
    返回：http起始行，headers, body
    """
    if ENTER in http_pkg:    # 处理非CLRF分割的http报文
        res = http_pkg_split(ENTER, http_pkg)
    elif CLRF in http_pkg:
        res = http_pkg_split(CLRF, http_pkg)

    return res


def http_pkg_split(sep, http_pkg):
    """
    http报文分割函数
    参数：http报文字符串，分割字符
    返回：http起始行，headers, body
    """
    tmp = http_pkg.split(sep=sep, maxsplit=1)

    start_line = tmp[0]

    others = tmp[1]     # 去除http报文起始行之后，剩余的内容应完全遵从MIME数据格式
    # 指定policy为HTTP，使得遵循 RFC 2822以及当前的各种 MIME RFC（RFC 5322, RFC 2047)
    p: parser.Parser = parser.Parser(policy=policy.HTTP)
    msg: Message = p.parsestr(text=others, headersonly=False)   # 解析header和body

    headers = {}
    for k, v in msg.items():
        headers[k] = str(v)

    body = msg.get_payload()

    return start_line, headers, body


class WebToolPoller(BasePoller):
        
    def processData(self, sock, fileno, datas):
        """
        处理接收数据
        """
        data_src = datas.decode('utf-8')
        log.INFO_MSG("RechargePoller prcessData:%s" % (data_src))

        global tonclient
        try:
            req = http_parse(data_src)[2]
            log.INFO_MSG(f'order info wating: {req}')
            playerId = json.loads(req)['playerId']
            boc = json.loads(req)['boc']
            prop_id = json.loads(req)['prop_id']
            try:
                KBEngine.baseAppData['Lobby'].onRecharge(playerId, boc, prop_id)
                log.DEBUG_MSG(f'transaction successful')
            except Exception as e:
                log.ERROR_MSG(f'decode BOC failed error: {e}')
        except Exception as e:
            log.ERROR_MSG(f'RechargePoller parse request error: {e}, datas:{datas}')
            self.closeRequest(sock, fileno, 400, 'FAILURE')
        else:
            try:
                pass
            except Exception as e1:
                log.ERROR_MSG(f'RechargePoller parse request error: {e1}')
                self.closeRequest(sock, fileno, 400, 'FAILURE')
            else:
                self.closeRequest(sock, fileno, 200, 'SUCCESS')

    def closeRequest(self, sock, fileno, code, msg):
        if fileno not in self._clients:
            print(f'RechargePoller already close {msg}')
            return

        response = 'HTTP/1.1 %d %s\r\n' % (code, STATUS_CODES.get(code, "UNKNOWN RESPONSE"))
        response += 'Content-Type: application/text\r\n'
        response += 'Content-Length: %d\r\n' % len(msg)
        response += '\r\n'
        response += msg

        sock.send(response.encode('utf-8'))
        KBEngine.deregisterReadFileDescriptor(sock.fileno())
        sock.close()
        del self._clients[fileno]
